#pragma once

#include <memory.h>

#include "command.hpp"
#include "components.hpp"

#include "baseCommandReceiver.hpp"

#include <entt/entt.hpp>

namespace qbe::undoRedoT {

template <typename T>
class ChangeObjCR : public BaseCommandReceiver {
 public:
  ChangeObjCR();
  ~ChangeObjCR();

  virtual bool Action(bool bUndo) override;
  void Prepare(entt::registry* _reg, entt::entity _entity, ecs::EventWriter<Event>& _event_writer, layout::LayoutCmd* _cmd, bool _use_cmd = true);
  void resetOldData();

 private:
  entt::registry* reg = nullptr;
  entt::entity entity = entt::null;

  layout::LayoutCmd* cmd;
  bool use_cmd;

  std::shared_ptr<T> ptr;
  ecs::EventWriter<Event>* event_writer;

  bool first{true};
  bool redo{false};
  bool undo{false};
};

template <typename T>
ChangeObjCR<T>::ChangeObjCR() {}

template <typename T>
ChangeObjCR<T>::~ChangeObjCR() {
  if (reg != nullptr && reg->try_get<InUndoRedoStack>(entity) != nullptr) {
    auto& in = reg->get<InUndoRedoStack>(entity);
    in.num--;

    if (in.num == 0) {
      reg->remove<InUndoRedoStack>(entity);

      if (reg->any_of<ObjDeleteTag>(entity)) {
        // reg->remove<ObjDeleteTag>(entity);
        // reg->emplace_or_replace<ObjDeleteForeverTag>(entity);
        // event_writer->send({Code::ObjDeleteForever});
      }
    }
  }
}

template <typename T>
void ChangeObjCR<T>::Prepare(entt::registry* _reg, entt::entity _entity, ecs::EventWriter<Event>& _event_writer, layout::LayoutCmd* _cmd,
                             bool _use_cmd) {
  reg = _reg;
  entity = _entity;

  ptr = reg->get<std::shared_ptr<T>>(entity);
  ptr = ptr->copy();
  // reg->emplace_or_replace<std::shared_ptr<T>>(entity, ptr->copy());

  redo = true;
  cmd = _cmd;

  event_writer = &_event_writer;

  use_cmd = _use_cmd;
}

template <typename T>
bool ChangeObjCR<T>::Action(bool bUndo) {
  if ((bUndo && !undo) || (!bUndo && !redo))
    return false;

  if (!reg->valid(entity)) {
    return false;
  }

  if (first) {
    if (reg->try_get<InUndoRedoStack>(entity) == nullptr) {
      reg->emplace<InUndoRedoStack>(entity);
    }

    auto& in = reg->get<InUndoRedoStack>(entity);
    in.num++;

    first = false;
    // reg->emplace_or_replace<ObjChangeTag>(entity);
    // event_writer->send({Code::ObjChange});
    // cmd->update<std::shared_ptr<T>>(entity);
  } else if (bUndo) {
    auto& ptr_src = reg->get<std::shared_ptr<T>>(entity);

    // auto ptr_t = ptr;
    // ptr = ptr_src;
    // ptr_src = ptr_t;

    T mid_t = *ptr_src;
    *ptr_src = *ptr;
    *ptr = mid_t;

    reg->emplace_or_replace<ObjChangeTag>(entity);
    // event_writer->send({Code::ObjChange});

    if (use_cmd)
      cmd->update(entity);
  } else {
    auto& ptr_src = reg->get<std::shared_ptr<T>>(entity);

    // auto ptr_t = ptr;
    // ptr = ptr_src;
    // ptr_src = ptr_t;

    auto mid_t = *ptr_src;
    *ptr_src = *ptr;
    *ptr = mid_t;

    reg->emplace_or_replace<ObjChangeTag>(entity);
    // event_writer->send({Code::ObjChange});

    if (use_cmd)
      cmd->update(entity);
  }

  undo = !undo;
  redo = !redo;

  return true;
}
template <typename T>
void ChangeObjCR<T>::resetOldData() {
  auto& ptr_src = reg->get<std::shared_ptr<T>>(entity);
  *ptr_src = *ptr;
}
}  // namespace qbe::undoRedoT